/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.pz.net.master;

import com.pz.net.Command;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.pz.util.Log.d;
import static com.pz.net.Command.*;
import static com.pz.util.ByteBufferHelper.*;

/**
 *
 * @author jannek
 */
public class Connection implements Runnable {

    private static LinkedList<Connection> available =
            new LinkedList<Connection>();

    private MasterServer ms;
    private SocketChannel channel;
    private ByteBuffer in;
    private ByteBuffer out;

    private Connection(MasterServer ms) {
        in = ByteBuffer.allocate(200);
        out = ByteBuffer.allocate(200);
        this.ms = ms;
    }

    public static void start(MasterServer ms, SocketChannel channel) {
        Connection con = available.poll();
        if (con == null)
            con = new Connection(ms);
        con.channel = channel;
        con.in.clear();
        con.out.clear();
        new Thread(con).start();
    }

    private void error(Command error) throws IOException {
        put(error, out);
        out.flip();
        channel.write(out);
        out.clear();
    }

    private Client getPlayer() {
        Client player = ms.getPlayer(in.getInt());
        if (player != null && player.isKey(in.getInt()))
            return player;
        return null;
    }

    private boolean validateUdp(Client player) throws IOException {
        for (int i = 0; i < 10 && player.getAddress() == null; i++) {
            error(Command.MASTER_UPDATE_UDP);
            sleep();
        }
        if (player.getAddress() == null) {
            error(Command.FAILED_TO_UPDATE_UDP);
            return false;
        }
        return true;
    }

    private void putAddress(SocketAddress address) {
        putString(address.toString());
    }

    private void putString(String string) {
        out.put((byte) string.length());
        out.put(string.getBytes());
    }

    private void sleep() {
        try {
            Thread.sleep(300);
        } catch (InterruptedException ex) {
            // meh
        }
    }

    @Override
    public void run() {
        Client player;
        Server server;
        int id;
        int key;
        short maxPlayers;
        String name;
        String game;
        byte[] data;
        try {
            if (channel.read(in) > 0) {

                in.flip();
                switch (getCommand(in)) {
                    case READ_FILE_INPUT:
                        ms.readInputFromFile(getPlayer());
                        break;
                    case NEW_SERVER:
                        /*
                         * _IN_
                         * server name (byte + text)
                         * game name AND version (byte + text)
                         * max players (short)
                         * game data (byte + byte arr)
                         * 
                         * _OUT_
                         * _OR_
                         * name taken (byte)
                         *
                         * _IF_ udp address is outdated:
                         * update udp (byte)
                         *  - repeat while udp addres is outdated max ten times
                         *  - if update successful continue
                         *  - else: filede to update udp (byte)
                         *
                         * server created (byte)
                         * server.id (int)
                         * server.key (int)
                         *
                         */
                        //server name
                        name = getString(in);
                        //game name
                        game = getString(in);
                        maxPlayers = in.getShort();
                        data = getBytes(in);

                        server = ms.createServer(name, game, maxPlayers, data);

                        if (server == null) {
                            error(Command.MASTER_NAME_TAKEN);
                            break;
                        }

                        if (!validateUdp(server))
                            break;

                        put(SERVER_CREATED, out);
                        out.putInt(server.id);
                        out.putInt(server.key);
                        out.flip();
                        channel.write(out);

                        break;
                    case LINK_UDP_UPDATE_SERVER:
                        break;
                    case CLOSE_SERVER:
                        break;
                    case LINK_GET_SERVER:
                        break;
                    case LINK_GET_SERVER_LIST:
                        synchronized(ms.serverList) {
                            ms.updateServerList();
                            channel.write(ms.serverList);
                            ms.serverList.flip();
                        }
                        break;
                    case LINK_CONNECT_TO_SERVER:
                        /*
                         * _IN_
                         * player.id (int)
                         * player.key (int)
                         *
                         * _OUT_
                         * wrong id or key (byte)
                         *
                         * _OR_
                         * validate udp()
                         *
                         *
                         *
                         */
                        id = in.getInt();
                        key = in.getInt();
                        player = ms.getPlayer(id);
                        if (player == null || !player.isKey(key)) {
                            error(Command.WRONG_ID_OR_KEY);
                            break;
                        }

                        if (!validateUdp(player))
                            break;
                        
                        server = ms.getServer(in.getInt());
                        if (server == null) {
                            error(Command.MASTER_NO_SUCH_SERVER);
                            break;
                        }

                        server.connect(player);
                        ms.addNotification(server);

                        put(LINK_CONNECT_TO_SERVER, out);
                        putAddress(server.getAddress());

                        break;
                    case NEW_PLAYER:
                        /*
                         * _IN_
                         * name (byte + text)
                         *
                         * _OUT_
                         * name taken (byte)
                         *
                         * _OR_
                         * player created (byte)
                         * p.id (int)
                         * p.key (int)
                         */
                        name = getString(in);
                        player = ms.createPlayer(name);
                        if (player == null)
                            error(Command.MASTER_NAME_TAKEN);
                        else {
                            put(PLAYER_CREATED, out);
                            out.putInt(player.id);
                            out.putInt(player.key);
                            out.flip();
                            channel.write(out);
                        }
                        break;
                    case GET_PLAYER:
                        break;
                }
            }
        } catch (IOException ioex) {
            ioex.printStackTrace();
        } catch (BufferUnderflowException bex) {
            bex.printStackTrace();
        }

        try {
            channel.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
        available.add(this);
    }

}
